﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System;
using System.Xml.Serialization;
using System.Collections;
using System.Runtime.Serialization;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Threading;
#if USE_HYPER
using Hyper.ComponentModel;
#endif

namespace BrainTechLLC.ThreadSafeObjects
{
    /// <summary>
    /// A multilevel dictionary provides quick access to items given a sequence of (multiple) keys
    /// </summary>    
#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
#endif
    [Serializable]
    [DataContract]
    public class MultiLevelDictionary<TKey, TItem> : INotifyCollectionChanged
    {
        [DataMember]
        public Dictionary<TKey, MultiLevelDictionary<TKey, TItem>> ItemLookup;
        [DataMember]
        public Dictionary<TKey, TItem> TerminatingItems;

        [NonSerialized, XmlIgnore]
        public Cached<List<KeyValuePair<List<TKey>, TItem>>> AllTerminatingItemsListCache;

        public int ItemCount
        {
            get
            {
                int count = 0;

                count += (TerminatingItems == null) ? 0 : TerminatingItems.Count;

                if (ItemLookup != null)
                    ItemLookup.ForEach((dict) => count += dict.Value.ItemCount);

                return count;
            }
        }

        public int MaxDepth(int startDepth)
        {
            int max = startDepth;
            if (ItemLookup != null)
            {
                foreach (var kvp in ItemLookup)
                {
                    if (kvp.Value != null)
                    {
                        int depth = kvp.Value.MaxDepth(startDepth + 1);
                        if (depth > max) max = depth;
                    }
                }
            }
            return max;
        }

        public void AtLevel(int level, int currentLevel, Action<Dictionary<TKey, TItem>> action, Action<int> levelTooDeep)
        {
            if (currentLevel == level && TerminatingItems != null)
            {
                action(TerminatingItems);
            }
            else
            {
                if (ItemLookup == null)
                {
                    levelTooDeep(currentLevel + 1);
                }
                else
                {
                    foreach (var v in ItemLookup)
                    {
                        if (v.Value != null)
                            v.Value.AtLevel(level, currentLevel + 1, action, levelTooDeep);
                    }
                }
            }
        }

        public Dictionary<List<TKey>, TItem> AllTerminatingItems
        {
            get { return ReadTerminatingItemsRecursive(new List<TKey>()); }
        }

        public List<KeyValuePair<List<TKey>, TItem>> AllTerminatingItemsList
        {
            get
            {
                if (AllTerminatingItemsListCache == null)
                {
                    Interlocked.CompareExchange<Cached<List<KeyValuePair<List<TKey>, TItem>>>>(ref AllTerminatingItemsListCache,
                         new Cached<List<KeyValuePair<List<TKey>, TItem>>>(delegate(object parameterIn, out List<KeyValuePair<List<TKey>, TItem>> results, object[] args)
                              {
                                  List<KeyValuePair<List<TKey>, TItem>> valuePairs = new List<KeyValuePair<List<TKey>, TItem>>();
                                  Dictionary<List<TKey>, TItem> items = ReadTerminatingItemsRecursive(new List<TKey>());

                                  foreach (KeyValuePair<List<TKey>, TItem> item in items)
                                      valuePairs.Add(item);

                                  results = valuePairs;
                                  return true;
                              }), null);
                }
                return AllTerminatingItemsListCache.GetItem(null, null);
            }
        }

        public bool ContainsValue(TItem item)
        {
            if (TerminatingItems != null)
            {
                bool contains = TerminatingItems.ContainsValue(item);

                if (contains)
                    return true;
            }

            if (ItemLookup != null)
            {
                foreach (var v in ItemLookup)
                {
                    bool contains = v.Value.ContainsValue(item);

                    if (contains)
                        return true;
                }
            }

            return false;
        }

        public Dictionary<List<TKey>, TItem> ReadTerminatingItemsRecursive(List<TKey> keysSoFar)
        {
            Dictionary<List<TKey>, TItem> allItems = new Dictionary<List<TKey>, TItem>();

            TerminatingItems.ForEachIfNotNull((item) =>
                 {
                     List<TKey> keysSoFarCopy = new List<TKey>(keysSoFar);
                     keysSoFarCopy.Add(item.Key);
                     allItems.Add(keysSoFarCopy, item.Value);
                 });

            if (ItemLookup != null)
            {
                foreach (KeyValuePair<TKey, MultiLevelDictionary<TKey, TItem>> item in ItemLookup)
                {
                    List<TKey> keysSoFarCopy = new List<TKey>(keysSoFar);
                    keysSoFarCopy.Add(item.Key);
                    Dictionary<List<TKey>, TItem> subItems = item.Value.ReadTerminatingItemsRecursive(keysSoFarCopy);
                    subItems.ForEachIfNotNull(subItem => allItems.Add(subItem.Key, subItem.Value));
                }
            }

            return allItems;
        }

        public bool RemoveTerminatingItem(int keyLevel, int maxLevel, params TKey[] keys)
        {
            TKey key = keys[keyLevel];

            if (keyLevel == maxLevel - 1)
            {
                TItem item = default(TItem);
                if (TerminatingItems != null && TerminatingItems.TryGetValue(key, out item))
                {
                    // possible race condition here
                    TerminatingItems.Remove(key);
                    AllTerminatingItemsListCache.SafeMarkDirty();
                    OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, item);
                    return true;
                }

                return false;
            }
            else
            {
                if (ItemLookup != null)
                {
                    MultiLevelDictionary<TKey, TItem> dictionary;

                    if (ItemLookup.TryGetValue(key, out dictionary))
                    {
                        bool found = dictionary.RemoveTerminatingItem(keyLevel + 1, maxLevel, keys);

                        if (found)
                            AllTerminatingItemsListCache.SafeMarkDirty();

                        return found;
                    }
                }

                return false;
            }
        }

        public bool RemoveTerminatingItem(int keyLevel, int maxLevel, List<TKey> keys)
        {
            TKey key = keys[keyLevel];

            if (keyLevel == maxLevel - 1)
            {
                TItem item = default(TItem);
                if (TerminatingItems != null && TerminatingItems.TryGetValue(key, out item))
                {
                    // possible race condition here
                    TerminatingItems.Remove(key);
                    AllTerminatingItemsListCache.SafeMarkDirty();
                    OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, item);
                    return true;
                }

                return false;
            }
            else
            {
                if (ItemLookup != null)
                {
                    MultiLevelDictionary<TKey, TItem> dictionary;

                    if (ItemLookup.TryGetValue(key, out dictionary))
                    {
                        bool found = dictionary.RemoveTerminatingItem(keyLevel + 1, maxLevel, keys);

                        if (found)
                            AllTerminatingItemsListCache.SafeMarkDirty();

                        return found;
                    }
                }

                return false;
            }
        }


        public bool RemoveTerminatingItem(params TKey[] keys)
        {
            int keysLength = keys.Length;

            if (keysLength == 0)
            {
                keys = new TKey[] { default(TKey) };
                keysLength = 1;
                //throw new Exception("Must have at least 1 key");
            }

            return RemoveTerminatingItem(0, keysLength, keys);
        }

        public bool RemoveTerminatingItem(List<TKey> keys)
        {
            int keysLength = keys.Count;

            if (keysLength == 0)
            {
                keys = new List<TKey>() { default(TKey) };
                keysLength = 1;
            }

            return RemoveTerminatingItem(0, keysLength, keys);
        }

        public void AddTerminatingItem(TItem item, int keyLevel, int maxLevel, params TKey[] keys)
        {
            TKey key = keys[keyLevel];

            if (keyLevel == maxLevel - 1)
            {
                Populate.OnDemand(ref TerminatingItems);

                if (!TerminatingItems.ContainsKey(key))
                {
                    // TODO: Think through race condition where key added twice
                    try { TerminatingItems.Add(key, item); }
                    catch { AddTerminatingItem(item, keyLevel, maxLevel, keys); }
                    OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);
                }
                else
                {
                    TItem old = default(TItem);
                    if (TerminatingItems.TryGetValue(key, out old))
                    {
                        TerminatingItems[key] = item;

                        if (CollectionChanged != null)
                            OnNotifyCollectionChanged(NotifyCollectionChangedAction.Replace, new List<TItem>() { old, item });
                    }
                }
            }
            else
            {
                Populate.OnDemand(ref ItemLookup);

                MultiLevelDictionary<TKey, TItem> dictionary;

                if (!ItemLookup.TryGetValue(key, out dictionary))
                {
                    MultiLevelDictionary<TKey, TItem> newDictionary = new MultiLevelDictionary<TKey, TItem>();

                    // TODO: Think through race condition where key added twice
                    try
                    {
                        ItemLookup.Add(key, newDictionary);
                        newDictionary.AddTerminatingItem(item, keyLevel + 1, maxLevel, keys);
                    }
                    catch { AddTerminatingItem(item, keyLevel, maxLevel, keys); }
                }
                else { dictionary.AddTerminatingItem(item, keyLevel + 1, maxLevel, keys); }
            }

            AllTerminatingItemsListCache.SafeMarkDirty();
        }

        public void AddTerminatingItem(TItem item, int keyLevel, int maxLevel, List<TKey> keys)
        {
            TKey key = keys[keyLevel];

            if (keyLevel == maxLevel - 1)
            {
                Populate.OnDemand(ref TerminatingItems);

                if (!TerminatingItems.ContainsKey(key))
                {
                    // TODO: Think through race condition where key added twice
                    try { TerminatingItems.Add(key, item); }
                    catch { AddTerminatingItem(item, keyLevel, maxLevel, keys); }
                    OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);
                }
                else
                {
                    TItem old = default(TItem);
                    if (TerminatingItems.TryGetValue(key, out old))
                    {
                        TerminatingItems[key] = item;

                        if (CollectionChanged != null)
                            OnNotifyCollectionChanged(NotifyCollectionChangedAction.Replace, new List<TItem>() { old, item });
                    }
                }
            }
            else
            {
                Populate.OnDemand(ref ItemLookup);

                MultiLevelDictionary<TKey, TItem> dictionary;

                if (!ItemLookup.TryGetValue(key, out dictionary))
                {
                    MultiLevelDictionary<TKey, TItem> newDictionary = new MultiLevelDictionary<TKey, TItem>();

                    // TODO: Think through race condition where key added twice
                    try
                    {
                        ItemLookup.Add(key, newDictionary);
                        newDictionary.AddTerminatingItem(item, keyLevel + 1, maxLevel, keys);
                    }
                    catch { AddTerminatingItem(item, keyLevel, maxLevel, keys); }
                }
                else { dictionary.AddTerminatingItem(item, keyLevel + 1, maxLevel, keys); }
            }

            AllTerminatingItemsListCache.SafeMarkDirty();
        }

        public void AddTerminatingItem(TItem item, params TKey[] keys)
        {
            int keysLength = keys.Length;

            if (keysLength == 0)
            {
                keys = new TKey[] { default(TKey) };
                keysLength = 1;
                //throw new Exception("Must have at least 1 key");
            }

            AddTerminatingItem(item, 0, keysLength, keys);
        }

        public void AddTerminatingItem(TItem item, List<TKey> keys)
        {
            int keysLength = keys.Count;

            if (keysLength == 0)
            {
                keys = new List<TKey>() { default(TKey) };
                keysLength = 1;
            }

            AddTerminatingItem(item, 0, keysLength, keys);
        }

        public TItem FindItem(int keyLevel, int maxLevel, params TKey[] keys)
        {
            TKey key = keys[keyLevel];

            if (keyLevel == maxLevel - 1)
            {
                if (TerminatingItems != null)
                {
                    TItem item;
                    if (TerminatingItems.TryGetValue(key, out item))
                        return item;
                }

                return default(TItem);
            }
            else
            {
                if (ItemLookup != null)
                {
                    MultiLevelDictionary<TKey, TItem> dictionary;

                    if (ItemLookup.TryGetValue(key, out dictionary))
                        return dictionary.FindItem(keyLevel + 1, maxLevel, keys);
                }

                return default(TItem);
            }
        }

        public TItem FindItem(int keyLevel, int maxLevel, List<TKey> keys)
        {
            TKey key = keys[keyLevel];

            if (keyLevel == maxLevel - 1)
            {
                if (TerminatingItems != null)
                {
                    TItem item;
                    if (TerminatingItems.TryGetValue(key, out item))
                        return item;
                }

                return default(TItem);
            }
            else
            {
                if (ItemLookup != null)
                {
                    MultiLevelDictionary<TKey, TItem> dictionary;

                    if (ItemLookup.TryGetValue(key, out dictionary))
                        return dictionary.FindItem(keyLevel + 1, maxLevel, keys);
                }

                return default(TItem);
            }
        }

        public TItem FindItem(params TKey[] keys)
        {
            int keysLength = keys.Length;

            if (keysLength == 0)
            {
                keys = new TKey[] { default(TKey) };
                keysLength = 1;
            }

            return FindItem(0, keysLength, keys);
        }

        public TItem FindItem(List<TKey> keys)
        {
            int keysLength = keys.Count;

            if (keysLength == 0)
            {
                keys = new List<TKey>() { default(TKey) };
                keysLength = 1;
            }

            return FindItem(0, keysLength, keys);
        }

        public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, IList<TItem> changedItems)
        {
            if (CollectionChanged != null)
            {
                switch (action)
                {
                    case NotifyCollectionChangedAction.Add:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action, changedItems));
                        break;
                    case NotifyCollectionChangedAction.Remove:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action, changedItems));
                        break;
                    case NotifyCollectionChangedAction.Replace:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action, changedItems[0], changedItems[1]));
                        break;
                    case NotifyCollectionChangedAction.Reset:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action));
                        break;
                }
            }
        }

        public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, TItem changedItem)
        {
            if (CollectionChanged != null)
                OnNotifyCollectionChanged(action, new List<TItem>() { changedItem });
        }

        #region INotifyCollectionChanged Members

#if NO_SILVERLIGHT
        [field: NonSerialized]
        public event NotifyCollectionChangedEventHandler CollectionChanged;
#else
        [field: NonSerialized]
        public event EventHandler<NotifyCollectionChangedEventArgsEx<TItem>> CollectionChanged;
#endif

        #endregion
    }
}